using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Diagnostics;
//using System.Windows.Controls;

namespace mates_viz
{

/// <summary>
/// This class provides access to the web service.
/// It keeps track of the the session ID to facilitate connection, but no further information
/// about state is stored in this class.
/// 
/// This class also transformes server data types into data types used by the rest of the
/// program.
/// 
/// </summary>
    class soapClient
    {

		public delegate void MatesMessageEvent(stateManager.message Message);
		public event MatesMessageEvent MessageReceivedEvent;
		public event MatesMessageEvent MessageSentEvent;

        public struct locGuess
        {
            public float score;
            public string source;
            public stateManager.matesLoc loc;
        }

        // list of location guesses
        public List<locGuess> locGuesses = new List<locGuess>();
        // list of nearby locations
        public List<stateManager.matesLoc> locsNearby = new List<stateManager.matesLoc>();
        public string sessionID;
        mates _mates = new mates(); // connection to the WSDL

        public bool doLogin(string user_name, string password)
        {
           
            login_response resp;
            resp = _mates.login(user_name, password);

            if (resp.success == 1)
            {
                Debug.WriteLine("Login successful. Session ID:" + resp.session_id);
                sessionID = resp.session_id; // set state to logged-in
                return true;
            }
            else
            {
                Debug.WriteLine("Login failed.  Why:" + resp.response);
                sessionID = "";  // set state to logged-out
                return false;
            }
        }
        public bool doLogout()
        {
            int resp = 0;

            try
            { resp = _mates.logout(sessionID); }
            catch (Exception e) { Debug.WriteLine("Logout failed.  Error: " + e.Message); }

            if (resp == 1)
            {
                Debug.WriteLine("Logout successful.");
                return true; }
            else
            {
                Debug.WriteLine("Logout failed.");
                return false; }

        }

        public bool refresh(stateManager stateMgr)
        {

            refresh_response resp;
            resp = _mates.refresh(sessionID);

            if (resp.success == 1)
            {

                foreach (loc_q_item q_item in resp.loc_q)
				{	// Add the location
					stateMgr.AddLocation(new stateManager.matesLoc(q_item.loc_id, q_item.name,
					 q_item.lat, q_item.@long, q_item.distance, q_item.direction, q_item.wall));
                    Debug.WriteLine(q_item.msgtype + " - ID:" + q_item.loc_id.ToString() + " " + q_item.name + " " + q_item.distance + " ft");
                }
				
                // Enqueue all new messages for display
                foreach (msg_q_item q_item in resp.msg_q)
                {
                    stateManager.message newMsg = new stateManager.message();
                    newMsg.from = q_item.username;
                    newMsg.text = q_item.msg;
                    newMsg.time = q_item.ts;
					MessageReceivedEvent(newMsg);
                }


				List<KeyValuePair<double, rel_q_item>> sortedUserLocQueue = new List<KeyValuePair<double,rel_q_item>>();
				List<rel_q_item> relQueue = new List<rel_q_item>();

				// split the rel_queue into two different queues for separate processing
				foreach (rel_q_item q_item in resp.rel_q)
				{
					if (q_item.reltype == "location")
					{
						// if it's a location message, put it in a sorted list so we can do
						// graphical additions most efficiently
						stateManager.matesLoc loc = stateMgr.GetLocation(q_item.loc_id);

						if (loc != null)
						{
							int itemBefore =
								sortedUserLocQueue.FindLastIndex(delegate(KeyValuePair<double, rel_q_item> kvp)
								{ return kvp.Key <= loc.Distance; });

							//if (itemBefore == -1) itemBefore = 0;	// if item wasn't found, index=0 of new item
							sortedUserLocQueue.Insert(itemBefore + 1, new KeyValuePair<double, rel_q_item>(loc.Distance, q_item));
						}
						else
						{
							Error.Log("soapClient::refresh: Could not find location distance!");
							sortedUserLocQueue.Add(new KeyValuePair<double, rel_q_item>(0, q_item));	// add anyway, to the end
						}
					}
					else
					{
						// otherwise add to the normal relQueue for processing after in_loc stuff
						relQueue.Add(q_item);
					}
				}

				// first, process the sorted location information
				foreach (KeyValuePair<double, rel_q_item> q_itemKVP in sortedUserLocQueue)
				{
					// reference the actual item value
					rel_q_item q_item = q_itemKVP.Value;

					switch (q_item.msgtype)
                    {
                        case "rel_add":
							// we don't need to check the reltype since we already know it's "location"
							//    because only location ones were added to this list (above)
							stateMgr.AddUser(new stateManager.matesUser(q_item.name, q_item.loc_id));
							stateManager.matesUser usr = stateMgr.GetUser(q_item.name);
							Debug.WriteLine(usr.Name + " is in loc " + usr.Loc_ID);
							break;
					}
				}

				// second, process all other relations
                foreach (rel_q_item q_item in relQueue)
                {
                    Debug.WriteLine("Rel. received:" + q_item.reltype + " user: " + q_item.name + "loc:" + q_item.loc_id + " subject: " + q_item.relparam);
					
                    switch (q_item.msgtype)
                    {
                        case "rel_add":
                            switch (q_item.reltype)
                            {
                                case "friend":      // friend relation type (ignore!)
                                    //stateMgr.AddRelation(new stateManager.matesRelation(stateManager.matesRelationType.Friend, q_item.name, q_item.relparam));
                                    break;
                                case "interest":    // interest relation type
                                    stateMgr.AddRelation(new stateManager.matesRelation(stateManager.matesRelationType.Interest, q_item.name, q_item.relparam));
                                    break;
                                case "course":      // course relation type
                                    stateMgr.AddRelation(new stateManager.matesRelation(stateManager.matesRelationType.Course, q_item.name, q_item.relparam));
                                    break;
                                case "fof":         // friend of friend relation type
									stateMgr.AddRelation(new stateManager.matesRelation(stateManager.matesRelationType.FriendOfFriend, q_item.name, q_item.relparam));
                                    break;
                                default:
                                    Debug.WriteLine("Unknown relation type: " + q_item.reltype);
                                    break;
                            }
                            break;  // end of rel_add

                        case "exitloc":    // user is exiting location to go elsewhere
                            stateMgr.FlushUser(q_item.name);
                            Debug.WriteLine("exitloc:" + q_item.name + "\n");
                            break;
                        case "logout":    // user is logging off of the system
                            stateMgr.FlushUser(q_item.name);
                            Debug.WriteLine("logout:" + q_item.name + "\n");
                            break;
                    }
                            
                }

                return true;
            }
            else
            {
                Debug.WriteLine("refresh failed. response:" + resp.response );
                return false;
            }
        }

        public bool setLocation(stateManager.matesLoc selectedLoc)
        {
            // Note: this function modifies the passed-in selectedLoc to include the new Loc ID

            setloc_response resp;
            resp = _mates.setloc(sessionID, selectedLoc.Name, (float)selectedLoc.Latitude, (float)selectedLoc.Longitude, 0, "00:08:74:4F:0B:73");
            Debug.WriteLine("Trying to set: " + selectedLoc.Name + " " + selectedLoc.Latitude + " " + selectedLoc.Longitude);
            if (resp.success == 1)
            {
                // create an updated version of the selected loc with the loc ID
                selectedLoc = new stateManager.matesLoc(resp.loc_id, selectedLoc.Name,
                                      selectedLoc.Latitude, selectedLoc.Longitude, 0, 0, "Refresh needed.");

                Debug.WriteLine("Server assigned loc ID: " + resp.loc_id.ToString());
                return true;
            }
            else
            {
                Debug.WriteLine("Failed to set location.  Response: " + resp.response);
                return false;
            }
        }
        public bool setLocation(int loc_id)
        {
            setloc_response resp;
            resp = _mates.setloc(sessionID, "", 0, 0, loc_id, "00:08:74:4F:0B:73");
            Debug.WriteLine("Trying to set by loc_id: " + loc_id);
            if (resp.success == 1)
            {
                Debug.WriteLine("Server assigned loc ID: " + resp.loc_id.ToString());
                return true;
            }
            else
            {
                Debug.WriteLine("Failed to set location.  Response: " + resp.response);
                return false;
            }
        }

        public bool getNearbyLocs(float lat, float lon)
        {
            getnearbylocs_response resp;
            resp = _mates.getnearbylocs(sessionID, lat, lon);

            if (resp.success == 1)
            {
                Debug.WriteLine("getNearbyLocs successful.  Returned " + resp.locs.Length + " locations");
                locsNearby.Clear(); // remove previous list
                foreach (getnearbylocs_location loc in resp.locs)
                {
                    stateManager.matesLoc nearLoc 
                        = new stateManager.matesLoc(loc.loc_id, loc.name, loc.lat, loc.@long, loc.distance, loc.direction, "No wall info.");
                    locsNearby.Add(nearLoc);
                }

                return true;
            }
            else
            {
                Debug.WriteLine("getNearbyLocs failed!  Response: " + resp.response);
                return false;
            }
        }
        static public string bearing(float bearing)
        {

            //string dist = String.Format("{0:F2}", distance) + " ft, "; //+ String.Format("{0:F0}", bearing) + " degrees";

            // return the direction in heading (N.E.S.W..)
            string dirn = "";
            if (bearing < 0) dirn = "?";
            else if (bearing < 22.5) dirn = "N";
            else if (bearing < 67.5) dirn = "NE";
            else if (bearing < 112.5) dirn = "E";
            else if (bearing < 157.5) dirn = "SE";
            else if (bearing < 202.5) dirn = "S";
            else if (bearing < 247.5) dirn = "SW";
            else if (bearing < 292.5) dirn = "W";
            else if (bearing < 337.5) dirn = "NW";
            else if (bearing < 360) dirn = "N";
            else dirn = "?";

            return dirn;

        }
        public bool getLocGuesses()
        {

            guessloc_response resp;
            resp = _mates.guessloc(sessionID, "00:08:74:4F:0B:73");
          
            if (resp.success == 1)
            {

                locGuesses.Clear(); // clear the existsing list if successful
                Debug.WriteLine("GetLocs successful.  Returned " + resp.locs.Length + " locations");
                foreach (guessloc_location loc in resp.locs)
                {
                    // Transform each response into a locGuess
                    locGuess newGuess = new locGuess();
                    newGuess.loc = new stateManager.matesLoc(loc.loc_id, loc.name, loc.lat, loc.@long);
                    newGuess.score = loc.score;
                    newGuess.source = loc.source;
                    locGuesses.Add(newGuess);
                }
                return true;
            }
            else
            {
                Debug.WriteLine("GetLocs failed!  Response: " + resp.response);
                return false;
            }
        }
        public bool Register(string username, string password)
        {
            generic_response resp;
            resp = _mates.register(username, password);

            if (resp.success == 1)
            {
                Debug.Print("User created successfully.  Response: " + resp.response);
                return true;
            }
            else
            {
                Debug.Print("Registration failed. Response: " + resp.response);
                return false;
            }

        }

        public bool sendMessage(string destUser, string messageText)
        {
            generic_response resp;
            resp = _mates.sendmsg(sessionID, destUser, messageText);

            Debug.Print("Dest user: " + destUser );

            if (resp.success == 1)
            {
                Debug.Print("Message sent successfully.");
				stateManager.message newMsg = new stateManager.message();
				newMsg.from = destUser;
				newMsg.text = messageText;
				newMsg.time = "now";
				MessageSentEvent(newMsg);
                return true;
            }
            else
            {
                Debug.Print("Message send failed! Response: " + resp.response);
                return false;
            }
        }

		public bool getFriends(List<string> friendList)
		{

			getfriends_response resp;
			resp = _mates.getfriends(sessionID);

			if (resp.success == 1)
			{
				friendList.Clear();
				Debug.WriteLine("GetFriends successful.  Returned " + resp.friends.Length + " friends");
				foreach (string friend in resp.friends)
					friendList.Add(friend);
				return true;
			}
			else
			{
				Debug.WriteLine("GetFriends failed!  Response: " + resp.response);
				return false;
			}
		}
		public bool addFriend(string newFriend)
        {
            generic_response resp;
            resp = _mates.friend_add(sessionID, newFriend);

            if (resp.success == 1) return true;
            else
            {   Debug.Print("Adding friend failed.  Response: " + resp.response);
                return false;	}
        }
		public bool removeFriend(string oldFriend)
		{
			generic_response resp;
			try
			{	resp = _mates.friend_del(sessionID, oldFriend);		}
			catch
			{	Error.Log("soapClient::removeFriend: SOAP call failed.");
				return false;		}

			if (resp.success == 1) return true;
			else
			{	Debug.Print("Removing friend failed.  Response: " + resp.response);
				return false;	}
		}

		public bool getInterests(List<string> interestList)
		{

			getinterests_response resp;
			resp = _mates.getinterests(sessionID);

			if (resp.success == 1)
			{
				interestList.Clear();
				Debug.WriteLine("GetInterests successful.  Returned " + resp.interests.Length + " interests");
				foreach (string interest in resp.interests)
					interestList.Add(interest);
				return true;
			}
			else
			{
				Debug.WriteLine("GetInterests failed!  Response: " + resp.response);
				return false;
			}
		}
        public bool addInterest(string newInterest)
        {
			
            generic_response resp;
			try
			{
				resp = _mates.interest_add(sessionID, newInterest);
			}
			catch 
			{
				Error.Log("soapClient::addInterest: SOAP call failed.");
				return false; 
			}

            if (resp.success == 1) return true;
            else
            {
                Debug.Print("Adding interest failed.  Response: " + resp.response);
                return false;
            }
        }
		public bool removeInterest(string oldInterest)
		{

			generic_response resp;
			try
			{
				resp = _mates.interest_del(sessionID, oldInterest);
			}
			catch
			{
				Error.Log("soapClient::removeInterest: SOAP call failed.");
				return false;
			}

			if (resp.success == 1) return true;
			else
			{
				Debug.Print("Removing interest failed.  Response: " + resp.response);
				return false;
			}
		}

		public bool getCourses(List<string> courseList)
		{

			getcourses_response resp;
			resp = _mates.getcourses(sessionID);

			if (resp.success == 1)
			{
				courseList.Clear();
				Debug.WriteLine("GetCourses successful.  Returned " + resp.courses.Length + " courses");
				foreach (string course in resp.courses)
					courseList.Add(course);
				return true;
			}
			else
			{
				Debug.WriteLine("GetCourses failed!  Response: " + resp.response);
				return false;
			}
		}
        public bool addCourse(string newCourse)
        {
            generic_response resp;
            resp = _mates.course_add(sessionID, newCourse);
            if (resp.success == 1) return true;
            else
            {
                Debug.Print("Adding course failed.  Response: " + resp.response);
                return false;
            }
        }
		public bool removeCourse(string oldCourse)
		{

			generic_response resp;
			try
			{
				resp = _mates.course_del(sessionID, oldCourse);
			}
			catch
			{
				Error.Log("soapClient::removeCourse: SOAP call failed.");
				return false;
			}

			if (resp.success == 1) return true;
			else
			{
				Debug.Print("Removing course failed.  Response: " + resp.response);
				return false;
			}
		}

    }
}